package com.fulihui.usercore.service;

import com.fulihui.usercore.api.UserService;
import com.fulihui.usercore.biz.shard.UserManager;
import com.fulihui.usercore.biz.shard.UserTokenManager;
import com.fulihui.usercore.biz.shard.WechatUserManager;
import com.fulihui.usercore.common.util.AppProps;
import com.fulihui.usercore.common.util.Errors;
import com.fulihui.usercore.condition.UserCondition;
import com.fulihui.usercore.core.counter.AllUsersCounter;
import com.fulihui.usercore.core.repository.*;
import com.fulihui.usercore.dal.dataobj.LoginDO;
import com.fulihui.usercore.dal.dataobj.LoginHelpDO;
import com.fulihui.usercore.dal.dataobj.WechatUserExample;
import com.fulihui.usercore.dto.AuthDTO;
import com.fulihui.usercore.dto.UserDTO;
import com.fulihui.usercore.dto.UserStatusDTO;
import com.fulihui.usercore.dto.WechatUserDTO;
import com.fulihui.usercore.enums.*;
import com.fulihui.usercore.request.user.*;
import com.fulihui.usercore.utils.CheckSmsCodeUtil;
import com.fulihui.usercore.utils.WeakPasswordDicUtil;
import org.apache.dubbo.config.annotation.Service;
import org.near.servicesupport.error.InvokeServiceException;
import org.near.servicesupport.request.BaseRequest;
import org.near.servicesupport.request.TPageRequest;
import org.near.servicesupport.request.TRequest;
import org.near.servicesupport.result.*;
import org.near.servicesupport.util.ServiceAssert;
import org.near.toolkit.common.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

import static java.util.Collections.emptyMap;
import static org.near.servicesupport.error.Errors.Commons.REQUEST_PARAMETER_ERROR;
import static org.near.servicesupport.error.Errors.Commons.SYSTEM_ERROR;
import static org.near.servicesupport.result.ResultBuilder.*;
import static org.near.toolkit.common.EnumUtil.inEnum;
import static org.near.toolkit.common.StringUtil.isBlank;
import static org.near.toolkit.common.StringUtil.isNotBlank;
import static org.springframework.util.CollectionUtils.isEmpty;

/**
 * @author Willard.Hu on 2016/5/17.
 */
@Service(version = "1.0.0")
public class UserServiceImpl implements UserService {
    private transient final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);

    private static final Pattern NICKNAME_REGX = Pattern.compile("^[\\w\\u4e00-\\u9fa5]+");

    @Autowired
    private UserRepository        userRepository;
    @Autowired
    private UserManager           userManager;
    @Autowired
    private WechatUserRepository  wechatUserRepository;
    @Autowired
    private UserAddressRepository userAddressRepository;
    @Autowired
    private UserTagRespitory      userTagRespitory;
    @Autowired
    private UserLoginRepository   userLoginRepository;
    @Autowired
    private UserTokenManager      userTokenManager;
    @Autowired
    private AllUsersCounter       allUsersCounter;
    @Autowired
    private WechatUserManager     wechatUserManager;
    @Autowired
    private AppProps              appProps;
    @Autowired
    private CheckSmsCodeUtil      checkSmsCodeUtil;
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private WechatOpenidHelpRepository wechatOpenidHelpRepository;
    @Autowired
    private LoginHelpRepository loginHelpRepository;
    /**
     * @see UserService#register(UserRegisterRequest)
     */
    @Override
    public TSingleResult<AuthDTO> register(UserRegisterRequest request) {
        String userId = userManager.register(request);
        return succTSingle(authDTO(request.getLoginId(), userId));
    }

    @Override
    public TSingleResult<Boolean> checkRegistered(UserCheckRegisteredRequest request) {
        boolean rs = userManager.checkRegistered(request);
        return succTSingle(rs);
    }

    @Override
    public TSingleResult<AuthDTO> login(UserLoginRequest request) {
        String userId = userManager.login(request);
        return succTSingle(authDTO(request.getLoginId(), userId));
    }

    @Override
    @Transactional
    public BaseResult modify(UserModifyRequest request) {
        if (StringUtil.isNotBlank(request.getGender())) {
            ServiceAssert.isTrue(inEnum(request.getGender(), UserGenderEnum.class),
                    REQUEST_PARAMETER_ERROR);
        }
        if (StringUtil.isNotBlank(request.getState())) {
            ServiceAssert.isTrue(inEnum(request.getState(), UserStateEnum.class),
                    REQUEST_PARAMETER_ERROR);
        }

        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(request, userDTO, "registerType", "gender", "state");
        userDTO.setGender(request.getGender());
        userDTO.setState(request.getState());

        if (userDTO.getNickname() != null) {
            if (StringUtil.equals(userDTO.getNickname(), StringUtil.EMPTY_STRING)) {
                userDTO.setNickname(null); // XXX，先放开空字符串
            } else {
                if (!NICKNAME_REGX.matcher(userDTO.getNickname()).matches()) {
                    // 用户昵称校验
                    LOG.error("用户昵称[{}]不能为空，也不能有特殊符号，只支持中文、数字、字母和下划线", userDTO.getNickname());
                    throw new InvokeServiceException(REQUEST_PARAMETER_ERROR);
                }
                userDTO.setNickname(StringUtil.filterEmoji(userDTO.getNickname()));
            }
        }
        boolean b = userRepository.modify(userDTO);
        if (!b) {
            fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult modifyPasswd(UserModifyPasswdRequest request) {
        userManager.modifyPasswd(request);
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult forgetPasswd(UserForgetPasswdRequest request) {
        userManager.forgetPasswd(request);
        return succ(BaseResult.class);
    }

    @Override
    public TSingleResult<Boolean> hasPasswd(UserIdRequest request) {
        boolean b = userManager.hasPasswd(request.getUserId());
        return succTSingle(b);
    }

    @Override
    public BaseResult modifyMobileNo(UserModifyMobileNoRequest request) {
        BooleanValueEnum booVal = null;
        if (request.isVerified()) {
            booVal = BooleanValueEnum.TRUE;
        }
        boolean b = userRepository.updateMobileNo(request.getMobileNo(), request.getUserId(),
                booVal);
        if (!b) {
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult modifyIdcard(UserModifyIdcardRequest request) {
        BooleanValueEnum booVal = null;
        if (request.isVerified()) {
            booVal = BooleanValueEnum.TRUE;
        }
        boolean b = userRepository.updateIdcard(request.getIdCard(), request.getUserId(), booVal);
        if (!b) {
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult modifyEmail(UserModifyEmailRequest request) {
        BooleanValueEnum booVal = null;
        if (request.isVerified()) {
            booVal = BooleanValueEnum.TRUE;
        }
        boolean b = userRepository.updateEmail(request.getEmail(), request.getUserId(), booVal);
        if (!b) {
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult appendIdentity(UserAppendIdentityRequest request) {
        LoginDO loginDO = userLoginRepository.queryByUserId(request.getUserId());
        loginDO.setIdentity(UserIdentity.append(loginDO.getIdentity(),
                request.getUserIdentityList()));
        boolean b = userLoginRepository.update(loginDO);
        if (!b) {
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public BaseResult removeIdentity(UserRemoveIdentityRequest request) {
        LoginDO loginDO = userLoginRepository.queryByUserId(request.getUserId());
        int newMerged = UserIdentity.remove(loginDO.getIdentity(), request.getUserIdentity());
        if (newMerged == 0) {
            LOG.error("用户不能没有身份，已有身份:{}，要移除的身份:{}", loginDO.getIdentity(), request
                    .getUserIdentity().name());
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        loginDO.setIdentity(newMerged);
        boolean b = userLoginRepository.update(loginDO);
        if (!b) {
            return fail(BaseResult.class, Errors.User.USER_MODIFY_FAILED);
        }
        return succ(BaseResult.class);
    }

    @Override
    public TSingleResult<UserDTO>  querySingle(UserSingleRequest request) {
        UserDTO userDTO;
        if (StringUtil.isNotBlank(request.getUserId())) {
            userDTO = userRepository.queryByPK(request.getUserId());
        } else if (StringUtil.isNotBlank(request.getLoginId())) {
            userDTO = userRepository.queryByLoginId(request.getLoginId());
        } else {
            LOG.error("用户userId与loginId不能都为空");
            return failTSingle(REQUEST_PARAMETER_ERROR);
        }
        return succTSingle(userDTO);
    }

    @Override
    public TMultiResult<UserDTO> queryByUserIds(TRequest<List<String>> request) {
        List<UserDTO> userList;
        if (isEmpty(request.getCondition())) {
            userList = Collections.emptyList();
        } else {
            userList = userRepository.queryByUserIds(request.getCondition());
        }
        return succTMulti(userList);
    }

    @Override
    public TSingleResult<Map<String, UserDTO>> queryByUserIdsToMap(TRequest<List<String>> request) {
        Map<String, UserDTO> userMap;
        if (isEmpty(request.getCondition())) {
            userMap = emptyMap();
        } else {
            userMap = userRepository.queryByUserIdsToMap(request.getCondition());
        }
        return succTSingle(userMap);
    }

    @Override
    public TPageResult<UserDTO> queryPage(TPageRequest<UserCondition> request) {
        int rows = request.getRows() > 100 ? 100 : request.getRows();
        UserCondition condition = request.getCondition();
        checkCondition(condition);

        if (isBlank(condition.getMobileNo())) {
            condition.setMobileNo(null);
        }

        List<UserDTO> userList = userRepository.queryPage(request.start4Mysql(), request.getRows(),
                request.getCondition());
        int totalCount = (int) userRepository.totalCount(request.getCondition());
        return succTPage(userList, request.getPage(), rows, totalCount);
    }

    @Override
    public TSingleResult<Long> countUser(TRequest<UserCondition> request) {
        UserCondition condition = request.getCondition();
        checkCondition(condition);
        long totalCount = userRepository.totalCount(request.getCondition());
        return succTSingle(totalCount);
    }

    private void checkCondition(UserCondition condition) {
        ServiceAssert.notNull(condition, REQUEST_PARAMETER_ERROR);
        Method[] methods = condition.getClass().getMethods();
        boolean check = false;
        for (Method m : methods) {
            if (m.getName().startsWith("get")) {
                try {
                    Object o = m.invoke(condition);
                    // 请求参数不能都为空
                    if (o != null) {
                        check = true;
                        break;
                    }
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        if (!check) {
            throw new InvokeServiceException(REQUEST_PARAMETER_ERROR);
        }
    }

    @Override
    public TSingleResult<Long> countAll() {
        long count = allUsersCounter.count();
        return succTSingle(count);
    }

    @Override
    public TSingleResult<WechatUserDTO> queryWechatUserInfo(UserWechatSingleRequest request) {
        WechatUserDTO wxUser = null;
        if (StringUtil.isNotBlank(request.getOpenId())) {
            wxUser = wechatOpenidHelpRepository.querySingle(request.getOpenId(),
                    WechatOpenIdTypeEnum.WECHAT_MP.getCode(), appProps.getWechatMpAppid());
        } else if (StringUtil.isNotBlank(request.getAppOpenId())) {
            wxUser = wechatOpenidHelpRepository.querySingle(request.getAppOpenId(),
                    WechatOpenIdTypeEnum.NATIVE_APP.getCode(), appProps.getWechatMpAppid());
        } else if (StringUtil.isNotBlank(request.getUserId())) {
            WechatUserExample example = new WechatUserExample();
            // 只支持福礼惠用户的公众平台和开放平台信息查询
            example.createCriteria().andUserIdEqualTo(request.getUserId())
                    .andAppidIn(Arrays.asList(appProps.getWechatMpAppid(), appProps.getWechatAppAppid()))
                    .andDisableEqualTo(0);
            List<WechatUserDTO> list = wechatUserRepository.query(example);
            if (!isEmpty(list)) {
                // list的结果数只有1或2两种情况
                if (list.size() == 1) {
                    wxUser = list.get(0);
                    if (wxUser.getAppid().equals(appProps.getWechatAppAppid())) {
                        wxUser.setAppOpenId(wxUser.getOpenId());
                    }
                } else {
                    WechatUserDTO first = list.get(0);
                    WechatUserDTO second = list.get(1);
                    if (first.getAppid().equals(appProps.getWechatMpAppid())) {
                        wxUser = first;
                        wxUser.setAppOpenId(second.getAppid());
                    } else {
                        wxUser = second;
                        wxUser.setAppOpenId(first.getAppid());
                    }
                }
            }
        } else {
            throw new InvokeServiceException(REQUEST_PARAMETER_ERROR);
        }
        if (wxUser != null) {
            // 手机端的支持
            if (StringUtil.isNotBlank(request.getAppOpenId())) {
                wxUser.setAppOpenId(wxUser.getOpenId());
            }
        }
        return succTSingle(wxUser);
    }

    @Override
    public TSingleResult<String> exchangeUserIdByToken(TRequest<String> request) {
        ServiceAssert.notBlank(request.getCondition(), REQUEST_PARAMETER_ERROR);
        AuthTokenRequest authTokenRequest = new AuthTokenRequest();
        authTokenRequest.setToken(request.getCondition());
        return exchangeUserIdByToken(authTokenRequest);
    }

    /**
     * @see UserService#exchangeUserIdByToken(AuthTokenRequest)
     */
    @Override
    public TSingleResult<String> exchangeUserIdByToken(AuthTokenRequest request) {
        String userId = userTokenManager.exchangeUserId(request.getToken());
        if (isNotBlank(userId)) {
            // 查到用户但如果用户登入
            UserDTO userDTO = userRepository.queryByPK(userId);
            if (userDTO != null && userDTO.getState().equals(UserStateEnum.FORBID.getCode())) {
                userTokenManager.removeToken(request.getToken());
                userId = null;
            }
        }
        return succTSingle(userId);
    }

    /**
     * @see UserService#removeToken(AuthTokenRequest)
     */
    @Override
    public BaseResult removeToken(AuthTokenRequest request) {
        boolean b = userTokenManager.removeToken(request.getToken());
        if (!b) {
            throw new InvokeServiceException(SYSTEM_ERROR);
        }
        return succ();
    }

    /**
     * @see UserService#bindingWechat(BindingWechatRequest)
     */
    @Override
    public BaseResult bindingWechat(BindingWechatRequest request) {
        ServiceAssert.isTrue(inEnum(request.getOpenIdType(), WechatOpenIdTypeEnum.class),
                REQUEST_PARAMETER_ERROR);
        if (StringUtil.isBlank(request.getUserId())){
            if (StringUtil.isBlank(request.getLoginId())){
                return ResultBuilder.fail(401,"绑定有误！");
            }
            LoginDO loginDO = loginHelpRepository.queryByPK(request.getLoginId());
            if (loginDO==null){
                return ResultBuilder.fail(401,"绑定有误！");
            }
            request.setUserId(loginDO.getUserId());
        }
        WechatUserDTO record = new WechatUserDTO();
        record.setOpenId(request.getOpenId());
        record.setOpenidType(request.getOpenIdType());
        record.setUnionid(request.getUnionid());
        record.setUserId(request.getUserId());
        wechatUserManager.bindingByUserId(record);
        return succ(BaseResult.class);
    }

    /**
     * @see UserService#queryDsiabledWechatUser(BaseRequest)
     */
    @Override
    public TMultiResult<WechatUserDTO> queryDsiabledWechatUser(BaseRequest request) {
        List<WechatUserDTO> wxUserList = wechatUserRepository.queryDsiabled();
        return succTMulti(wxUserList);
    }

    /**
     * @see UserService#updateWechatUserDisable(UpdateWechatUserDisableRequest)
     */
    @Override
    public BaseResult updateWechatUserDisable(UpdateWechatUserDisableRequest request) {
        boolean b = wechatUserRepository.updateDsiable(request.getDisable(), request.getUserId());
        if (!b) {
            throw new InvokeServiceException(SYSTEM_ERROR);
        }
        return succ(BaseResult.class);
    }

    /**
     * @see UserService#queryUseridsByLoginIds(LoginIdsRequest)
     */
    @Override
    public TSingleResult<Map<String, String>> queryUseridsByLoginIds(LoginIdsRequest request) {
        ServiceAssert.isTrue(!isEmpty(request.getLoginIds()) && request.getLoginIds().size() <= 100,
                REQUEST_PARAMETER_ERROR);
        List<LoginDO> list = loginHelpRepository.queryByLoginIds(request.getLoginIds());
        Map<String, String> map;
        if (!isEmpty(list)) {
            map = new HashMap<>();
            list.forEach(it -> map.put(it.getLoginId(), it.getUserId()));
        } else {
            map = emptyMap();
        }
        return succTSingle(map);
    }

    /**
     * @see UserService#modifyState(UserModifyStateRequest)
     */
    @Override
    public BaseResult modifyState(UserModifyStateRequest request) {
        ServiceAssert.isTrue(inEnum(request.getState(), UserStateEnum.class), REQUEST_PARAMETER_ERROR);
        boolean b = userRepository.updateState(request.getState(), request.getUserId());
        return b ? succ() : fail(SYSTEM_ERROR);
    }

    @Override
    public TSingleResult<Boolean> hasPasswdByLoginId(LoginIdRequest request) {
        ServiceAssert.notBlank(request.getLoginId(),REQUEST_PARAMETER_ERROR);
        //因为分表，先查询辅助表
        LoginDO loginHelp = loginHelpRepository.queryByPK(request.getLoginId());
        LoginDO loginDO = null;
        if (loginHelp != null){
            loginDO = userLoginRepository.queryByUserId(loginHelp.getUserId());
        }

        return succTSingle(loginDO != null && StringUtil.isNotBlank(loginDO.getPassword()));
    }

    @Override
    public BaseResult setPasswd(UserSetPasswdRequest request) {
        if (!checkSmsCodeUtil.checkSmsCode(request.getLoginId(), request.getVcode(),
                request.getVcodeNamespace())) {
            throw new InvokeServiceException(Errors.VCode.VCODE_VALIDATION_FAILS);
        }
        userManager.setPasswd(request);
        return succ(BaseResult.class);
    }

    @Override
    public TSingleResult<UserStatusDTO> checkUserStatus(UserCheckStatusRequest request) {
        ServiceAssert.notBlank(request.getLoginId(),REQUEST_PARAMETER_ERROR);
        UserCheckRegisteredRequest registeredRequest = new UserCheckRegisteredRequest();
        registeredRequest.setLoginId(request.getLoginId());
        registeredRequest.setOutUniqueId(request.getOutUniqueId());
        registeredRequest.setRegisterType(UserRegisterTypeEnum.MOBILE.getCode());
        boolean registered = userManager.checkRegistered(registeredRequest);
        UserStatusDTO userStatusDTO = new UserStatusDTO();
        if (registered){
            //如果注册过，检测是否设置过密码
            LoginDO loginDO = userLoginRepository.queryByPK(request.getLoginId());
            userStatusDTO.setRegistered(registered);
            userStatusDTO.setHasPasswd(loginDO != null && StringUtil.isNotBlank(loginDO.getPassword()));
        }
        return succTSingle(userStatusDTO);
    }

    @Override
    public BaseResult checkWeakPassword(WeakPasswordCheckRequest request) {
        ServiceAssert.notBlank(request.getPasswd(),REQUEST_PARAMETER_ERROR);
        if (!WeakPasswordDicUtil.passwordMap.containsKey(request.getPasswd())){
            return succ();
        }
        return fail(103,"您输入的密码过于简单，请重新输入！");
    }

    /**
     * 用户完善手机号接口
     *
     * @param request
     * @return
     */
    @Override
    public BaseResult completeMobile(UserLoginMobileRequest request) {
        LOG.info("补充手机号方法请求参数：{}",request);
        ServiceAssert.notBlank(request.getUserId(),REQUEST_PARAMETER_ERROR);
        ServiceAssert.notBlank(request.getMobileno(),REQUEST_PARAMETER_ERROR);
        ServiceAssert.notBlank(request.getVcode(),REQUEST_PARAMETER_ERROR);
        ServiceAssert.notBlank(request.getVcodeNamespace(),REQUEST_PARAMETER_ERROR);
        //校验手机号是否已经被绑定过
        LoginDO loginDOOld = userLoginRepository.queryByPK(request.getMobileno());
        if (loginDOOld != null){
            throw new InvokeServiceException(Errors.User.LOGIN_HAS_BINDING);
        }
        if (!checkSmsCodeUtil.checkSmsCode(request.getMobileno(), request.getVcode(),
                request.getVcodeNamespace())) {
            throw new InvokeServiceException(Errors.VCode.VCODE_VALIDATION_FAILS);
        }
        // 使用事务模版将事务往上提
        boolean flag = transactionTemplate.execute(status->{
            //fixme 如果补充手机号，调用此方法，证明usr_login表的login_id存的是unionid,需把辅助表的数据删掉，再重新插入一条
            LoginDO oldLoginDO = userLoginRepository.queryByUserId(request.getUserId());
            if (oldLoginDO == null){
                status.setRollbackOnly();
                return false;
            }
            //删除 旧的数据  login_id(unionid)
            boolean delete = loginHelpRepository.delete(oldLoginDO.getLoginId());
            if (!delete){
                status.setRollbackOnly();
                return false;
            }
            //重新插入一条
            LoginHelpDO record = new LoginHelpDO();
            record.setUserId(request.getUserId());
            record.setLoginId(request.getMobileno());
            boolean insert = loginHelpRepository.insert(record);
            if (!insert){
                status.setRollbackOnly();
                return false;
            }
            //修改登录表
            LoginDO loginDO = new LoginDO();
            loginDO.setUserId(request.getUserId());
            loginDO.setLoginId(request.getMobileno());
            boolean update1 = userLoginRepository.updateLoginId(loginDO);


            //修改用户详情表
            UserDTO userDTO = new UserDTO();
            userDTO.setUserId(request.getUserId());
            userDTO.setMobileNo(request.getMobileno());
            userDTO.setMemo(request.getMobileno());
            boolean update2 = userRepository.modify(userDTO);
            if(update1 && update2){
                return true;
            }
            status.setRollbackOnly();
            return false;
        });

        if(flag){
            return ResultBuilder.succ();
        }
        return ResultBuilder.fail(500,"手机号补充失败");
    }

    private AuthDTO authDTO(String loginId, String userId) {
        String token = userTokenManager.createToken(loginId, userId);
        AuthDTO authDTO = new AuthDTO();
        authDTO.setUserId(userId);
        authDTO.setToken(token);
        return authDTO;
    }
}
